﻿using System.Collections.Generic;

using Godot.core.io;
using Godot.core;

namespace Godot.modules.gdscript
{
    public partial class GDCompiler
    {
        public class CodeGen
        {
            public GDScript script;
            public GDParser.ClassNode class_node;
            public GDParser.FunctionNode function_node;

            public bool debug_stack;

            public List<Dictionary<StringName, int>> stack_id_stack;
            public Dictionary<StringName, int> stack_identifiers;

            public List<GDFunction.StackDebug> stack_debug;
            public List<Dictionary<StringName, int>> block_identifier_stack;
            public Dictionary<StringName, int> block_identifiers;

            public Dictionary<Variant, int> constant_map;
            public Dictionary<StringName, int> name_map;

            public List<int> opcodes;

            public int current_line;
            public int stack_max;
            public int call_max;

            public CodeGen()
            {
                stack_id_stack = new List<Dictionary<StringName, int>>();
                stack_identifiers = new Dictionary<StringName, int>();

                block_identifier_stack = new List<Dictionary<StringName, int>>();
                block_identifiers = new Dictionary<StringName, int>();

                constant_map = new Dictionary<Variant, int>();
                name_map = new Dictionary<StringName, int>();

                opcodes = new List<int>();
            }

            public void add_stack_identifier(StringName p_id, int p_stackpos)
            {
                stack_identifiers[p_id] = p_stackpos;
                if (debug_stack)
                {
                    block_identifiers[p_id] = p_stackpos;
                    GDFunction.StackDebug sd = new GDFunction.StackDebug();
                    sd.added = true;
                    sd.line = current_line;
                    sd.identifier = p_id;
                    sd.pos = p_stackpos;
                    stack_debug.Add(sd);
                }
            }

            public void push_stack_identifiers()
            {

                stack_id_stack.Add(stack_identifiers);
                if (debug_stack)
                {

                    block_identifier_stack.Add(block_identifiers);
                    block_identifiers.Clear();
                }
            }

            public void pop_stack_identifiers() 
            {
                stack_identifiers = stack_id_stack[stack_id_stack.Count - 1];
                stack_id_stack.RemoveAt(stack_id_stack.Count - 1);

                if (debug_stack) 
                {
                    foreach(KeyValuePair<StringName,int> E in block_identifiers)
                    {

                        GDFunction.StackDebug sd = new GDFunction.StackDebug();
                        sd.added=false;
                        sd.identifier= E.Key;
                        sd.line=current_line;
                        sd.pos = E.Value;
                        stack_debug.Add(sd);
                    }
                    block_identifiers = block_identifier_stack[block_identifier_stack.Count - 1]; ;
                    block_identifier_stack.RemoveAt(block_identifier_stack.Count - 1);
                }
            }

            public int get_name_map_pos(StringName p_identifier)
            {
                int ret;
                if (!name_map.ContainsKey(p_identifier))
                {
                    ret = name_map.Count;
                    name_map[p_identifier] = ret;
                }
                else
                {
                    ret = name_map[p_identifier];
                }
                return ret;
            }

            public int get_constant_pos(Variant p_constant) 
            {
			    if (constant_map.ContainsKey(p_constant))
				    return constant_map[p_constant];
			    
                int pos = constant_map.Count;
			    constant_map[p_constant] = pos;
			    return pos;
		    }

            public void alloc_stack(int p_level)
            {
                if (p_level >= stack_max)
                    stack_max = p_level + 1;
            }

            public void alloc_call(int p_params) 
            { 
                if (p_params >= call_max) 
                    call_max = p_params; 
            }
        }

        private int _err_line;
        private int _err_column;
        private string _error;
        private StringName _source;
        private GDParser _parser;

        public string error
        {
            get { return _error; }
        }

        public int error_line
        {
	        get { return _err_line; }
        }

        public int error_column
        {
            get { return _err_column; }
        }

        public GDCompiler()
        {
        }

        public Error compile(GDParser p_parser, GDScript p_script) 
        {
	        _err_line = -1;
	        _err_column =- 1;
	        _error = "";
	        _parser = p_parser;
	        
            GDParser.Node root = _parser.get_parse_tree();
	        
            if(root.type != GDParser.Node.Type.TYPE_CLASS)
                return Error.ERR_INVALID_DATA;

	        _source = p_script.get_path();

	        Error err = _parse_class(p_script, null,(GDParser.ClassNode)(root));

	        if (err != null)
		        return err;

	        return Error.OK;
        }

        

        
    }
}
